<?php

class PathModel
{

    protected $c;

    protected $path;

    function __construct($path, $c)
    {
        $this->path = $path;
        $this->c = $c;
    }

    function update($id, $value)
    {
        //验证
        $this->verify($value);

        //更新value, with path, with updateTime
        $result = $this->c->update(['id' => $id],
            [
                '$set' => [
                    join('.', $this->path) => $value,
                    'stat.updateTime' => time()
                ]
            ]
        );

        if ($result['n'] === 0) throw IdNotExistException($id, $this->c->getName());
    }

    function find($id, $fields = null)
    {
        $model = $this->c->findOne(
            ['id' => $id],
            $this->toMongoFields($fields)
        );

        if ($model === null) {
            throw new IdNotExistException($id, $this->c->getName());
        }

        return $this->toReturn($model);
    }

    function findUnique($cond, $fields = null)
    {
        //find
        $model = $this->c->findOne(
            $this->toMongoCond($cond),
            $this->toMongoFields($fields)
        );

        if ($model === null) return null;

        return $this->toReturn($model);
    }

    function findBy($args)
    {
        $fields = key_exists('fields', $args) ? $args['fields'] : null;
        $cond = key_exists('cond', $args) ? $args['cond'] : [];
        $orderBy = key_exists('orderBy', $args) ? $args['orderBy'] : '.stat.createTime dsc';
        $skip = $args['skip'];
        $limit = $args['limit'];

        $cursor = $this->c->find(
            $this->toMongoCond($cond),
            $this->toMongoFields($fields)
        )->sort($this->toMongoOrderBy($orderBy))
            ->skip($skip)->limit($limit);

        //list & total
        $count = $cursor->count();
        $list = iterator_to_array($cursor);

        //改造list以清除外套, with path
        foreach ($list as & $item) {
            $item = $this->toReturn($item);
        }

        return ['total' => $count, 'list' => $list];
    }

    private function toMongoFields($fields)
    {
        if (is_string($fields)) $fields = [$fields];

        $mongoFields = ['_id' => 0];

        if ($fields === null) {
            $mongoFields[join('.', $this->path)] = 1;
        } else {
            foreach ($fields as $field) {
                $mongoFields[join('.', $this->path) . '.' . $field] = 1;
            }
        }

        return $mongoFields;

    }

    private function toMongoCond($cond)
    {
        $mongoCond = [];

        foreach ($cond as $field => $value) {
            $mongoCond[$this->absolutePath($field)] = $value;
        }

        return $mongoCond;
    }

    private function toMongoOrderBy($orderBy)
    {
        if (is_string($orderBy)) $orderBy = [$orderBy];

        $mongoOrderBy = [];

        foreach ($orderBy as $orderToken) {
            $orderTokens = explode(' ', $orderToken);
            $orderField = $this->absolutePath($orderTokens[0]);
            $orderType = (count($orderTokens) > 1) ? $orderTokens[1] : 'asc';
            switch ($orderType) {
                case 'asc':
                    $mongoOrderBy[$orderField] = 1;
                    break;
                case 'dsc':
                    $mongoOrderBy[$orderField] = -1;
                    break;
                default:
                    throw new InvalidArgumentException(
                        'Unknown order type: ' . $orderToken);
            }
        }

        return $mongoOrderBy;
    }

    private function toReturn($value)
    {
        foreach ($this->path as $pathToken) {
            if (key_exists($pathToken, $value)) $value = $value[$pathToken];
            else return null;
        }
        return $value;
    }

    private function absolutePath($path)
    {
        if ($path[0] == '.') {
            return substr($path, 1);
        } else {
            return join('.', $this->path) . '.' . $path;
        }
    }

}